import arrow

from jose import jwt
from starlette.authentication import AuthCredentials, AuthenticationBackend, BaseUser

from config.config import SECRET_KEY, TOKEN_LIFETIME
from config.database import Session
from .exceptions import *


class Authentication(AuthenticationBackend):
    async def authenticate(self, request):
        if "token" not in request.headers:
            return

        if request.scope.get("path").endswith("/login"):
            if request.scope.get("method") == "POST":
                return

        token = request.headers.get("token")

        try:
            id, scope, type = decode_token(token)
        except HTTPException as exc:
            if exc.status_code == HTTP_461_TOKEN_EXPIRED:
                raise AuthenticationExpired("TOKEN过期")
            else:
                raise AuthenticationError("TOKEN错误")

        if type != "token":
            raise AuthenticationError("TOKEN错误")

        auth = AuthenticationUser(id, scope)

        if auth.get_user():
            if auth.scope == "admin":
                if not (set(auth.user.roles) & set((1,))):
                    raise AuthenticationError("权限错误, 未授权管理员权限")
        else:
            raise AuthenticationError("认证失败")

        return AuthCredentials([scope]), auth


class AuthenticationUser(BaseUser):
    def __init__(self, id, scope):
        self.id = id
        self.scope = scope
        self.user = None

    @property
    def is_authenticated(self):
        if self.user:
            return True
        else:
            return False

    @property
    def display_name(self):
        return f"{self.id}:{self.scope}"

    def get_user(self):
        from models import Manager

        if self.user:
            return self.user

        with Session() as session, session.begin():
            if self.scope in ("admin",):
                self.user = session.get(Manager, self.id)
            else:
                pass

            if self.user:
                session.expunge(self.user)

        return self.user


def authentication(auth, scope=None, superadmin=False):
    if not auth.is_authenticated:
        raise HTTPException(detail="未认证用户", status_code=HTTP_401_UNAUTHORIZED)

    if scope:
        if auth.scope != scope:
            raise HTTPException(detail="认证失败", status_code=HTTP_401_UNAUTHORIZED)

    if superadmin:
        if not auth.user.superadmin:
            raise HTTPException(
                detail="非超级管理员, 权限错误", status_code=HTTP_405_METHOD_NOT_ALLOWED
            )

    return auth.user


def make_token(id, scope, exp=True):
    t = arrow.utcnow().to("Asia/Shanghai").replace(hour=4)

    data = {
        "id": id,
        "scope": scope,
        "type": "token",
    }

    if exp:
        data["exp"] = t.shift(days=TOKEN_LIFETIME).datetime

    return jwt.encode(data, SECRET_KEY, algorithm="HS256")


def decode_token(token):
    try:
        data = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
    except jwt.ExpiredSignatureError:
        raise HTTPException(detail="TOKEN过期", status_code=HTTP_461_TOKEN_EXPIRED)
    except BaseException:
        raise HTTPException(detail="TOKEN错误", status_code=HTTP_401_UNAUTHORIZED)

    id = data["id"]
    scope = data["scope"]
    type = data["type"]

    try:
        id = int(id)
    except BaseException:
        raise HTTPException(detail="TOKEN错误", status_code=HTTP_401_UNAUTHORIZED)

    return id, scope, type
